use std::{
	ffi::{CStr, CString},
	path::PathBuf,
};

use crate::{Fallback, Stretch, Style, Weight, XFontType};

#[allow(non_snake_case)]
pub struct XFont {
	lib: *mut core::ffi::c_void,
	AFontMatcher_create: Option<extern "system" fn() -> *mut AFontMatcher>,
	AFontMatcher_destroy: Option<extern "system" fn(matcher: *mut AFontMatcher)>,
	AFontMatcher_setStyle: Option<extern "system" fn(matcher: *mut AFontMatcher, weight: u16, italic: bool)>,
	AFontMatcher_match: Option<
		extern "system" fn(
			matcher: *const AFontMatcher,
			familyName: *const core::ffi::c_char,
			text: *const u16,
			textLength: u32,
			runLengthOut: *mut u32,
		) -> *mut AFont,
	>,

	AFont_close: Option<extern "system" fn(font: *mut AFont)>,
	AFont_getFontFilePath: Option<extern "system" fn(font: *const AFont) -> *const core::ffi::c_char>,
	AFont_getCollectionIndex: Option<extern "system" fn(font: *const AFont) -> usize>,
}
impl Default for XFont {
	fn default() -> Self {
		unsafe {
			use std::mem::transmute;
			use std::ptr;
			let lib = dlopen(b"libandroid.so\0".as_ptr() as _, RTLD_LAZY | RTLD_LOCAL);
			if lib.is_null() {
				Self {
					lib: ptr::null_mut(),
					AFontMatcher_create: None,
					AFontMatcher_destroy: None,
					AFontMatcher_setStyle: None,
					AFontMatcher_match: None,
					AFont_close: None,
					AFont_getFontFilePath: None,
					AFont_getCollectionIndex: None,
				}
			} else {
				Self {
					lib,
					AFontMatcher_create: transmute(dlsym(lib, b"AFontMatcher_create\0".as_ptr() as _)),
					AFontMatcher_destroy: transmute(dlsym(lib, b"AFontMatcher_destroy\0".as_ptr() as _)),
					AFontMatcher_setStyle: transmute(dlsym(lib, b"AFontMatcher_setStyle\0".as_ptr() as _)),
					AFontMatcher_match: transmute(dlsym(lib, b"AFontMatcher_match\0".as_ptr() as _)),
					AFont_close: transmute(dlsym(lib, b"AFont_close\0".as_ptr() as _)),
					AFont_getFontFilePath: transmute(dlsym(lib, b"AFont_getFontFilePath\0".as_ptr() as _)),
					AFont_getCollectionIndex: transmute(dlsym(lib, b"AFont_getCollectionIndex\0".as_ptr() as _)),
				}
			}
		}
	}
}
impl XFontType for XFont {
	fn fallback(&self, text: &str, name: &str, weight: Weight, stretch: Stretch, style: Style) -> Fallback {
		self.fallback_impl(text, name, weight, stretch, style).unwrap_or_default()
	}
	fn system() -> String {
		"sans-serif".to_string()
	}
}
impl XFont {
	fn fallback_impl(&self, text: &str, name: &str, weight: Weight, _stretch: Stretch, style: Style) -> Option<Fallback> {
		let text = if text.is_empty() { "a" } else { text };
		unsafe {
			let matcher = self.AFontMatcher_create?();
			if matcher.is_null() {
				return None;
			}
			let matcher = FontMatcher(matcher, self.AFontMatcher_destroy);
			self.AFontMatcher_setStyle?(matcher.0, weight.0, matches!(style, Style::Italic));
			let mut run_out = 0;
			let family_name = CString::new(name).ok()?;
			let mut text: Vec<u16> = text.encode_utf16().collect();
			text.push(0);
			let font = self.AFontMatcher_match?(matcher.0, family_name.as_ptr(), text.as_ptr(), (text.len() - 1) as _, &mut run_out);
			if font.is_null() {
				return None;
			}
			let font = Font(font, self.AFont_close);
			let path = self.AFont_getFontFilePath?(font.0);
			if path.is_null() {
				return None;
			}
			let file = PathBuf::from(CStr::from_ptr(path).to_str().ok()?);
			let index = self.AFont_getCollectionIndex?(font.0);
			Some(Fallback {
				index: Some(index as _),
				unique: None,
				file,
			})
		}
	}
}

pub struct FontMatcher(*mut AFontMatcher, Option<extern "system" fn(matcher: *mut AFontMatcher)>);
impl Drop for FontMatcher {
	fn drop(&mut self) {
		if let Some(fun) = self.1 {
			fun(self.0);
		}
	}
}

pub struct Font(*mut AFont, Option<extern "system" fn(font: *mut AFont)>);
impl Drop for Font {
	fn drop(&mut self) {
		if let Some(fun) = self.1 {
			fun(self.0);
		}
	}
}

const RTLD_LAZY: core::ffi::c_int = 1; // from libloading for only android
const RTLD_LOCAL: core::ffi::c_int = 0; // from libloading for only android
#[link(name = "dl")]
extern "C" {
	fn dlopen(filename: *const core::ffi::c_char, flags: core::ffi::c_int) -> *mut core::ffi::c_void;
	fn dlclose(handle: *mut core::ffi::c_void) -> core::ffi::c_int;
	fn dlsym(handle: *mut core::ffi::c_void, symbol: *const core::ffi::c_char) -> *mut core::ffi::c_void;
	fn dlerror() -> *mut core::ffi::c_char;
}

pub const AFONT_WEIGHT_MIN: core::ffi::c_uint = 0;
pub const AFONT_WEIGHT_THIN: core::ffi::c_uint = 100;
pub const AFONT_WEIGHT_EXTRA_LIGHT: core::ffi::c_uint = 200;
pub const AFONT_WEIGHT_LIGHT: core::ffi::c_uint = 300;
pub const AFONT_WEIGHT_NORMAL: core::ffi::c_uint = 400;
pub const AFONT_WEIGHT_MEDIUM: core::ffi::c_uint = 500;
pub const AFONT_WEIGHT_SEMI_BOLD: core::ffi::c_uint = 600;
pub const AFONT_WEIGHT_BOLD: core::ffi::c_uint = 700;
pub const AFONT_WEIGHT_EXTRA_BOLD: core::ffi::c_uint = 800;
pub const AFONT_WEIGHT_BLACK: core::ffi::c_uint = 900;
pub const AFONT_WEIGHT_MAX: core::ffi::c_uint = 1000;
pub const AFAMILY_VARIANT_DEFAULT: core::ffi::c_uint = 0;
pub const AFAMILY_VARIANT_COMPACT: core::ffi::c_uint = 1;
pub const AFAMILY_VARIANT_ELEGANT: core::ffi::c_uint = 2;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct AFont {
	_unused: [u8; 0],
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct AFontMatcher {
	_unused: [u8; 0],
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct ASystemFontIterator {
	_unused: [u8; 0],
}
